{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "49e9c69e",
      "metadata": {
        "id": "49e9c69e"
      },
      "source": [
        "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/pinecone-io/examples/blob/master/learn/generation/langchain/handbook/06-langchain-agents.ipynb) [![Open nbviewer](https://raw.githubusercontent.com/pinecone-io/examples/master/assets/nbviewer-shield.svg)](https://nbviewer.org/github/pinecone-io/examples/blob/master/learn/generation/langchain/handbook/06-langchain-agents.ipynb)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1571b632",
      "metadata": {
        "id": "1571b632"
      },
      "source": [
        "#### [LangChain Handbook](https://www.pinecone.io/learn/series/langchain/)\n",
        "\n",
        "# Agents 🤖\n",
        "\n",
        "Agents are like \"tools\" for LLMs. They allow a LLM to access Google search, perform complex calculations with Python, and even make SQL queries.\n",
        "\n",
        "In this notebook we'll explore agents and how to use them in LangChain.\n",
        "\n",
        "We'll start by installing the prerequisite libraries that we'll be using in this example."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "uZR3iGJJtdDE",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "uZR3iGJJtdDE",
        "outputId": "42668cd1-2da7-48ab-9005-17e49f61f3b9"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.0/1.0 MB\u001b[0m \u001b[31m44.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m65.3/65.3 kB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m209.2/209.2 kB\u001b[0m \u001b[31m18.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.5/2.5 MB\u001b[0m \u001b[31m85.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m363.0/363.0 kB\u001b[0m \u001b[31m27.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m45.2/45.2 kB\u001b[0m \u001b[31m3.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.9/50.9 kB\u001b[0m \u001b[31m4.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h  Building wheel for google-search-results (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "  Building wheel for wikipedia (setup.py) ... \u001b[?25l\u001b[?25hdone\n"
          ]
        }
      ],
      "source": [
        "!pip install -qU \\\n",
        "  langchain==0.3.25 \\\n",
        "  langchain-openai==0.3.22 \\\n",
        "  langchain-experimental==0.3.4 \\\n",
        "  numexpr==2.11.0 \\\n",
        "  google-search-results==2.4.2 \\\n",
        "  wikipedia==1.4.0 \\\n",
        "  sqlalchemy==2.0.41"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "wPdWz1IdxyBR",
      "metadata": {
        "id": "wPdWz1IdxyBR"
      },
      "source": [
        "To run this notebook, we will need to use an OpenAI LLM. Here we will setup the LLM we will use for the whole notebook, just input your openai api key when prompted."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "c02c4fa2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "c02c4fa2",
        "outputId": "bb3423e5-71b8-4d7b-e16b-9025cc3c020b"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Enter your OpenAI API key: ··········\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "from getpass import getpass\n",
        "\n",
        "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\") \\\n",
        "    or getpass(\"Enter your OpenAI API key: \")\n",
        "\n",
        "OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "73bfcbb6",
      "metadata": {
        "id": "73bfcbb6"
      },
      "outputs": [],
      "source": [
        "from langchain_openai import ChatOpenAI\n",
        "\n",
        "llm = ChatOpenAI(\n",
        "    openai_api_key=OPENAI_API_KEY,\n",
        "    model_name=\"gpt-4.1-mini\",\n",
        "    temperature=0.0,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd60a97c",
      "metadata": {
        "id": "bd60a97c"
      },
      "source": [
        "With all of that set up, let's jump into **Agents**."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6e1f31b4",
      "metadata": {
        "id": "6e1f31b4"
      },
      "source": [
        "## What is an agent?"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b919c3a",
      "metadata": {
        "id": "5b919c3a"
      },
      "source": [
        "**Definition**: The key behind agents is giving LLM's the possibility of using tools in their workflow. This is where langchain departs from the popular chatgpt implementation and we can start to get a glimpse of what it offers us as builders. Until now, we covered several building blocks in isolation. Let's see them come to life.\n",
        "\n",
        "The official definition of agents is the following:\n",
        "\n",
        "\n",
        "> Agents use an LLM to determine which actions to take and in what order. An action can either be using a tool and observing its output, or returning to the user."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6c9c13e9",
      "metadata": {
        "id": "6c9c13e9"
      },
      "source": [
        "In this edition we will cover what we may call 'generic' agents which really able to perform many meta tasks. There are other more specific agents that are tuned for different tasks (called 'agent-toolkits'), but we will cover those in a future edition."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a93dc920",
      "metadata": {
        "id": "a93dc920"
      },
      "source": [
        "## Create database"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b601f903",
      "metadata": {
        "id": "b601f903"
      },
      "source": [
        "We will use the agents to interact with a small sample database of stocks. We will not dive into the details because this is just a dummy tool we will build for illustrative purposes. Let's create it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "61b1f17c",
      "metadata": {
        "id": "61b1f17c"
      },
      "outputs": [],
      "source": [
        "from sqlalchemy import MetaData\n",
        "\n",
        "metadata_obj = MetaData()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "3cc1d80e",
      "metadata": {
        "id": "3cc1d80e"
      },
      "outputs": [],
      "source": [
        "from sqlalchemy import Column, Integer, String, Table, Date, Float\n",
        "\n",
        "stocks = Table(\n",
        "    \"stocks\",\n",
        "    metadata_obj,\n",
        "    Column(\"obs_id\", Integer, primary_key=True),\n",
        "    Column(\"stock_ticker\", String(4), nullable=False),\n",
        "    Column(\"price\", Float, nullable=False),\n",
        "    Column(\"date\", Date, nullable=False),\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "c9a9571a",
      "metadata": {
        "id": "c9a9571a"
      },
      "outputs": [],
      "source": [
        "from sqlalchemy import create_engine\n",
        "\n",
        "engine = create_engine(\"sqlite:///:memory:\")\n",
        "metadata_obj.create_all(engine)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "81c3081f",
      "metadata": {
        "id": "81c3081f"
      },
      "outputs": [],
      "source": [
        "from datetime import datetime\n",
        "\n",
        "observations = [\n",
        "    [1, 'ABC', 200, datetime(2023, 1, 1)],\n",
        "    [2, 'ABC', 208, datetime(2023, 1, 2)],\n",
        "    [3, 'ABC', 232, datetime(2023, 1, 3)],\n",
        "    [4, 'ABC', 225, datetime(2023, 1, 4)],\n",
        "    [5, 'ABC', 226, datetime(2023, 1, 5)],\n",
        "    [6, 'XYZ', 810, datetime(2023, 1, 1)],\n",
        "    [7, 'XYZ', 803, datetime(2023, 1, 2)],\n",
        "    [8, 'XYZ', 798, datetime(2023, 1, 3)],\n",
        "    [9, 'XYZ', 795, datetime(2023, 1, 4)],\n",
        "    [10, 'XYZ', 791, datetime(2023, 1, 5)],\n",
        "]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "85fd20fa",
      "metadata": {
        "id": "85fd20fa"
      },
      "outputs": [],
      "source": [
        "from sqlalchemy import insert\n",
        "\n",
        "def insert_obs(obs):\n",
        "    stmt = insert(stocks).values(\n",
        "    obs_id=obs[0],\n",
        "    stock_ticker=obs[1],\n",
        "    price=obs[2],\n",
        "    date=obs[3]\n",
        "    )\n",
        "\n",
        "    with engine.begin() as conn:\n",
        "        conn.execute(stmt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "6766f1f7",
      "metadata": {
        "id": "6766f1f7"
      },
      "outputs": [],
      "source": [
        "for obs in observations:\n",
        "    insert_obs(obs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "H3_tIYndeNAt",
      "metadata": {
        "id": "H3_tIYndeNAt"
      },
      "source": [
        "We are installing the `langchain_experimental` library here, since the `SQLDatabaseChain` is located there. This might be changed in the future and moved into official `langchain` library."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "9721648e",
      "metadata": {
        "id": "9721648e"
      },
      "outputs": [],
      "source": [
        "from langchain.utilities import SQLDatabase\n",
        "from langchain_experimental.sql import SQLDatabaseChain\n",
        "\n",
        "db = SQLDatabase(engine)\n",
        "sql_chain = SQLDatabaseChain.from_llm(llm=llm, db=db, verbose=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6325f758",
      "metadata": {
        "id": "6325f758"
      },
      "source": [
        "## Agent types"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d732b7a",
      "metadata": {
        "id": "4d732b7a"
      },
      "source": [
        "In this section we will review several agents and see how they 'think' and what they can do."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8ee98631",
      "metadata": {
        "id": "8ee98631"
      },
      "source": [
        "Using one of langchain's pre-built agents involves three variables:\n",
        "* defining the tools or the toolkit\n",
        "* defining the llm\n",
        "* defining the agent type\n",
        "\n",
        "This is all really easy to do in langchain, as we will see in the following example."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1eaf306a",
      "metadata": {
        "id": "1eaf306a"
      },
      "source": [
        "### Agent type #1: Zero Shot React"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "Tgn6dRLEcxli",
      "metadata": {
        "id": "Tgn6dRLEcxli"
      },
      "source": [
        "In this first example we will use slightly different type of agent - SQL Agent which can be instantiated with it's own method `create_sql_agent`. Other agents will be instantiated in more generic way as we will see below in other examples.\n",
        "<br><br>\n",
        "This method uses *toolkit* instead of simple list of `tools`. You can read more about them in the [documentation](https://python.langchain.com/docs/integrations/tools/). For this use case, we will use `SQLDatabaseToolkit`."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mie_37ERl_ac",
      "metadata": {
        "id": "mie_37ERl_ac"
      },
      "source": [
        "As the name suggests, we will use this agent to perform 'zero shot' tasks on the input. That means that we will not have several, interdependent interactions but only one. In other words, this agent will have no memory."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "O_pYBlCcl11l",
      "metadata": {
        "id": "O_pYBlCcl11l"
      },
      "source": [
        "Now we are ready to initialize the agent! We will use `verbose` in `True` so we can see what is our agent's 'thinking' process."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "GtSMUCaIlupp",
      "metadata": {
        "id": "GtSMUCaIlupp"
      },
      "source": [
        "**Important Note:** *When interacting with agents it is really important to set the `max_iterations` parameters because agents can get stuck in infinite loops that consume plenty of tokens. The default value is 15 to allow for many tools and complex reasoning but for most applications you should keep it much lower.*"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "5Z4EmMmqiOvZ",
      "metadata": {
        "id": "5Z4EmMmqiOvZ"
      },
      "outputs": [],
      "source": [
        "from langchain.agents import create_sql_agent\n",
        "from langchain.agents.agent_toolkits import SQLDatabaseToolkit\n",
        "from langchain.agents.agent_types import AgentType\n",
        "\n",
        "agent_executor = create_sql_agent(\n",
        "    llm=llm,\n",
        "    toolkit=SQLDatabaseToolkit(db=db, llm=llm),\n",
        "    verbose=True,\n",
        "    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,\n",
        "    max_iterations=5\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "iBqz6aFzj-2d",
      "metadata": {
        "id": "iBqz6aFzj-2d"
      },
      "source": [
        "Let's see our newly created agent in action! We will ask it a question that involves a math operation over the stock prices."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "MdvgpwHRic3W",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "MdvgpwHRic3W",
        "outputId": "7679d826-5d91-4656-847a-3b743f609a29"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new SQL Agent Executor chain...\u001b[0m\n",
            "\u001b[32;1m\u001b[1;3mAction: sql_db_list_tables\n",
            "Action Input: \u001b[0m\u001b[38;5;200m\u001b[1;3mstocks\u001b[0m\u001b[32;1m\u001b[1;3mThought: There is a table named \"stocks\" which likely contains stock prices. I should check the schema of the \"stocks\" table to understand its columns and see if it contains stock prices and dates.\n",
            "Action: sql_db_schema\n",
            "Action Input: stocks\u001b[0m\u001b[33;1m\u001b[1;3m\n",
            "CREATE TABLE stocks (\n",
            "\tobs_id INTEGER NOT NULL, \n",
            "\tstock_ticker VARCHAR(4) NOT NULL, \n",
            "\tprice FLOAT NOT NULL, \n",
            "\tdate DATE NOT NULL, \n",
            "\tPRIMARY KEY (obs_id)\n",
            ")\n",
            "\n",
            "/*\n",
            "3 rows from stocks table:\n",
            "obs_id\tstock_ticker\tprice\tdate\n",
            "1\tABC\t200.0\t2023-01-01\n",
            "2\tABC\t208.0\t2023-01-02\n",
            "3\tABC\t232.0\t2023-01-03\n",
            "*/\u001b[0m\u001b[32;1m\u001b[1;3mThought: The table \"stocks\" contains the stock ticker, price, and date. I need to get the prices for 'ABC' and 'XYZ' on January 3rd and January 4th, then calculate the ratio of prices for each day and multiply those two ratios.\n",
            "\n",
            "I will first query the prices for 'ABC' and 'XYZ' on January 3rd and January 4th.\n",
            "\n",
            "Action: sql_db_query_checker\n",
            "Action Input: \n",
            "SELECT stock_ticker, price, date \n",
            "FROM stocks \n",
            "WHERE stock_ticker IN ('ABC', 'XYZ') \n",
            "AND date IN ('2023-01-03', '2023-01-04')\n",
            "ORDER BY date, stock_ticker\u001b[0m\u001b[36;1m\u001b[1;3mSELECT stock_ticker, price, date \n",
            "FROM stocks \n",
            "WHERE stock_ticker IN ('ABC', 'XYZ') \n",
            "AND date IN ('2023-01-03', '2023-01-04')\n",
            "ORDER BY date, stock_ticker\u001b[0m\u001b[32;1m\u001b[1;3mAction: sql_db_query\n",
            "Action Input: \n",
            "SELECT stock_ticker, price, date \n",
            "FROM stocks \n",
            "WHERE stock_ticker IN ('ABC', 'XYZ') \n",
            "AND date IN ('2023-01-03', '2023-01-04')\n",
            "ORDER BY date, stock_ticker\u001b[0m\u001b[36;1m\u001b[1;3m[('ABC', 232.0, '2023-01-03'), ('XYZ', 798.0, '2023-01-03'), ('ABC', 225.0, '2023-01-04'), ('XYZ', 795.0, '2023-01-04')]\u001b[0m\u001b[32;1m\u001b[1;3mThought: I have the prices for ABC and XYZ on January 3rd and January 4th. I need to calculate the ratio of ABC to XYZ on January 3rd, then the ratio of ABC to XYZ on January 4th, and finally multiply these two ratios.\n",
            "\n",
            "Price on 2023-01-03: ABC = 232.0, XYZ = 798.0  \n",
            "Price on 2023-01-04: ABC = 225.0, XYZ = 795.0  \n",
            "\n",
            "Ratio on 2023-01-03 = 232.0 / 798.0  \n",
            "Ratio on 2023-01-04 = 225.0 / 795.0  \n",
            "\n",
            "Multiplication = (232.0 / 798.0) * (225.0 / 795.0)\n",
            "\n",
            "Calculating:  \n",
            "232.0 / 798.0 ≈ 0.2907  \n",
            "225.0 / 795.0 ≈ 0.2830  \n",
            "Multiplication ≈ 0.2907 * 0.2830 ≈ 0.0823\n",
            "\n",
            "Final Answer: The multiplication of the ratio between stock prices for 'ABC' and 'XYZ' on January 3rd and January 4th is approximately 0.0823.\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n",
            "Result: The multiplication of the ratio between stock prices for 'ABC' and 'XYZ' on January 3rd and January 4th is approximately 0.0823.\n"
          ]
        }
      ],
      "source": [
        "result = agent_executor.invoke(\n",
        "    \"What is the multiplication of the ratio between stock \" +\n",
        "    \"prices for 'ABC' and 'XYZ' in January 3rd and the ratio \" +\n",
        "    \"between the same stock prices in January the 4th?\"\n",
        ")\n",
        "print(f\"Result: {result['output']}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "LqzuNMgWmFXS",
      "metadata": {
        "id": "LqzuNMgWmFXS"
      },
      "source": [
        "As always, let's see what the prompt is here:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "751a79df",
      "metadata": {
        "id": "751a79df",
        "outputId": "4349d268-6b3b-4e27-8779-6d16e94f13e1",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "You are an agent designed to interact with a SQL database.\n",
            "Given an input question, create a syntactically correct sqlite query to run, then look at the results of the query and return the answer.\n",
            "Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 10 results.\n",
            "You can order the results by a relevant column to return the most interesting examples in the database.\n",
            "Never query for all the columns from a specific table, only ask for the relevant columns given the question.\n",
            "You have access to tools for interacting with the database.\n",
            "Only use the below tools. Only use the information returned by the below tools to construct your final answer.\n",
            "You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.\n",
            "\n",
            "DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.\n",
            "\n",
            "If the question does not seem related to the database, just return \"I don't know\" as the answer.\n",
            "\n",
            "\n",
            "{tools}\n",
            "\n",
            "Use the following format:\n",
            "\n",
            "Question: the input question you must answer\n",
            "Thought: you should always think about what to do\n",
            "Action: the action to take, should be one of [{tool_names}]\n",
            "Action Input: the input to the action\n",
            "Observation: the result of the action\n",
            "... (this Thought/Action/Action Input/Observation can repeat N times)\n",
            "Thought: I now know the final answer\n",
            "Final Answer: the final answer to the original input question\n",
            "\n",
            "Begin!\n",
            "\n",
            "Question: {input}\n",
            "Thought: I should look at the tables in the database to see what I can query.  Then I should query the schema of the most relevant tables.\n",
            "{agent_scratchpad}\n"
          ]
        }
      ],
      "source": [
        "print(agent_executor.agent.runnable.steps[1].template)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b2b47ed",
      "metadata": {
        "id": "5b2b47ed"
      },
      "source": [
        "The question we must ask ourselves here is: how are agents different than chains?"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f73aaf07",
      "metadata": {
        "id": "f73aaf07"
      },
      "source": [
        "If we look at the agent's logic and the prompt we have just printed we will see some clear differences. First, we have the tools which are included in the prompt. Second we have a thought process that now involves a 'thought', 'action', 'action input', 'observation' sequence.\n",
        "\n",
        "All of these intermediate thoughts are written to the `agent_scratchpad`, ensuring that each _next step_ of the LLM is fully aware of previous steps.\n",
        "\n",
        "The LLM now has the ability to _reason_ over multiple steps on how to best solve a query — and use tools to do so. If you want to learn more about this paradigm (MRKL) in detail, please refer to [this paper](https://arxiv.org/pdf/2205.00445)."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "04d70642",
      "metadata": {
        "id": "04d70642"
      },
      "source": [
        "### Agent type #2: Conversational React"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1ec589b1",
      "metadata": {
        "id": "1ec589b1"
      },
      "source": [
        "The zero shot agent is really interesting but, as we said before, it has no memory. What if we want an assistant that remembers things we have talked about and can also reason about them and use tools? For that we have the conversational react agent."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5c26c50f",
      "metadata": {
        "id": "5c26c50f"
      },
      "source": [
        "We will use the math tool in this example and load it as below:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "4b6faff3",
      "metadata": {
        "id": "4b6faff3"
      },
      "outputs": [],
      "source": [
        "# Use the modern tool-based approach instead of deprecated LLMMathChain\n",
        "from langchain_core.tools import tool\n",
        "import math\n",
        "import numexpr\n",
        "\n",
        "@tool\n",
        "def calculator(expression: str) -> str:\n",
        "    \"\"\"Calculate expression using Python's numexpr library.\n",
        "\n",
        "    Expression should be a single line mathematical expression\n",
        "    that solves the problem.\n",
        "\n",
        "    Examples:\n",
        "        \"37593 * 67\" for \"37593 times 67\"\n",
        "        \"37593**(1/5)\" for \"37593^(1/5)\"\n",
        "        \"10000 * (1 + 0.08)**5\" for compound interest\n",
        "    \"\"\"\n",
        "    local_dict = {\"pi\": math.pi, \"e\": math.e}\n",
        "    return str(\n",
        "        numexpr.evaluate(\n",
        "            expression.strip(),\n",
        "            global_dict={},  # restrict access to globals\n",
        "            local_dict=local_dict,  # add common mathematical functions\n",
        "        )\n",
        "    )\n",
        "\n",
        "tools = [calculator]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "be45b903",
      "metadata": {
        "id": "be45b903"
      },
      "source": [
        "The memory type being used here is a simple buffer memory to allow us to remember previous steps in the reasoning chain. For more information on memory, please refer to the 3rd chapter of this series."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "0aff4edf",
      "metadata": {
        "id": "0aff4edf",
        "outputId": "8f83dfc7-0733-4479-f4b5-869d330612d6",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "/tmp/ipython-input-17-4074810399.py:3: LangChainDeprecationWarning: Please see the migration guide at: https://python.langchain.com/docs/versions/migrating_memory/\n",
            "  memory = ConversationBufferMemory(\n"
          ]
        }
      ],
      "source": [
        "from langchain.memory import ConversationBufferMemory\n",
        "\n",
        "memory = ConversationBufferMemory(\n",
        "    memory_key=\"chat_history\",\n",
        "    return_messages=True\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "6579cef0",
      "metadata": {
        "id": "6579cef0"
      },
      "outputs": [],
      "source": [
        "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
        "from langchain_core.runnables.base import RunnableSerializable\n",
        "from langchain_core.messages import HumanMessage, AIMessage, ToolMessage\n",
        "from langchain_core.tools import tool\n",
        "\n",
        "# Add a final_answer tool as recommended in the guide\n",
        "@tool\n",
        "def final_answer(answer: str, tools_used: list[str]) -> str:\n",
        "    \"\"\"Use this tool to provide a final answer to the user.\n",
        "    The answer should be in natural language as this will be provided\n",
        "    to the user directly. The tools_used must include a list of tool\n",
        "    names that were used within the `scratchpad`.\n",
        "    \"\"\"\n",
        "    return {\"answer\": answer, \"tools_used\": tools_used}\n",
        "\n",
        "# Add tools\n",
        "tools = [final_answer, calculator]\n",
        "\n",
        "# First, create a prompt that forces the LLM to analyze the history\n",
        "history_analysis_prompt = ChatPromptTemplate.from_messages([\n",
        "    (\"system\", (\n",
        "        \"Analyze the conversation history below and identify any calculations that have already been performed. \"\n",
        "        \"Extract the results of these calculations so they can be reused instead of recalculating.\"\n",
        "    )),\n",
        "    MessagesPlaceholder(variable_name=\"chat_history\"),\n",
        "    (\"human\", \"What calculations have been done and what are their results?\"),\n",
        "])\n",
        "\n",
        "# Then the main agent prompt\n",
        "agent_prompt = ChatPromptTemplate.from_messages([\n",
        "    (\"system\", (\n",
        "        \"You're a helpful assistant. When answering a user's question \"\n",
        "        \"you should first use one of the tools provided. After using a \"\n",
        "        \"tool the tool output will be provided in the \"\n",
        "        \"'scratchpad' below. If you have an answer in the \"\n",
        "        \"scratchpad you should not use any more tools and \"\n",
        "        \"instead answer directly to the user. \"\n",
        "        \"IMPORTANT: Use the analysis of previous calculations to avoid recalculating.\"\n",
        "    )),\n",
        "    (\"human\", \"Previous calculations analysis: {history_analysis}\"),\n",
        "    (\"human\", \"{input}\"),\n",
        "    MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
        "])\n",
        "\n",
        "# define the agent runnable with history analysis first\n",
        "agent: RunnableSerializable = (\n",
        "    {\n",
        "        \"input\": lambda x: x[\"input\"],\n",
        "        \"chat_history\": lambda x: x[\"chat_history\"],\n",
        "        \"agent_scratchpad\": lambda x: x.get(\"agent_scratchpad\", [])\n",
        "    }\n",
        "    | {\n",
        "        \"history_analysis\": lambda x: llm.invoke(history_analysis_prompt.format_messages(\n",
        "            chat_history=x[\"chat_history\"]\n",
        "        )).content,\n",
        "        \"input\": lambda x: x[\"input\"],\n",
        "        \"agent_scratchpad\": lambda x: x.get(\"agent_scratchpad\", [])\n",
        "    }\n",
        "    | agent_prompt\n",
        "    | llm.bind_tools(tools, tool_choice=\"auto\")\n",
        ")\n",
        "\n",
        "# create tool name to function mapping as per guide\n",
        "name2tool = {tool.name: tool.func for tool in tools}\n",
        "\n",
        "class CustomAgentExecutor:\n",
        "    def __init__(self, max_iterations: int = 5):\n",
        "        self.max_iterations = max_iterations\n",
        "        self.chat_history = []  # Simple list to store conversation history\n",
        "        self.agent = agent\n",
        "\n",
        "    def invoke(self, input: str) -> dict:\n",
        "        # invoke the agent but we do this iteratively in a loop until\n",
        "        # reaching a final answer\n",
        "        count = 0\n",
        "        agent_scratchpad = []\n",
        "\n",
        "        while count < self.max_iterations:\n",
        "            # invoke a step for the agent to generate a tool call\n",
        "            tool_call = self.agent.invoke({\n",
        "                \"input\": input,\n",
        "                \"chat_history\": self.chat_history,\n",
        "                \"agent_scratchpad\": agent_scratchpad\n",
        "            })\n",
        "\n",
        "            # add initial tool call to scratchpad\n",
        "            agent_scratchpad.append(tool_call)\n",
        "\n",
        "            # Handle ALL tool calls, not just the first one\n",
        "            if tool_call.tool_calls:\n",
        "                for tool_call_obj in tool_call.tool_calls:\n",
        "                    tool_name = tool_call_obj[\"name\"]\n",
        "                    tool_args = tool_call_obj[\"args\"]\n",
        "                    tool_call_id = tool_call_obj[\"id\"]\n",
        "\n",
        "                    # execute the tool\n",
        "                    tool_out = name2tool[tool_name](**tool_args)\n",
        "\n",
        "                    # add the tool output to the agent scratchpad\n",
        "                    tool_exec = ToolMessage(\n",
        "                        content=f\"{tool_out}\",\n",
        "                        tool_call_id=tool_call_id\n",
        "                    )\n",
        "                    agent_scratchpad.append(tool_exec)\n",
        "\n",
        "                    # add a print so we can see intermediate steps\n",
        "                    print(f\"{count}: {tool_name}({tool_args}) -> {tool_out}\")\n",
        "\n",
        "                count += 1\n",
        "\n",
        "                # Check if any tool call is the final answer tool\n",
        "                if any(tc[\"name\"] == \"final_answer\" for tc in tool_call.tool_calls):\n",
        "                    # Get the final answer from the final_answer tool\n",
        "                    final_tool_call = next(tc for tc in tool_call.tool_calls if tc[\"name\"] == \"final_answer\")\n",
        "                    final_answer = final_tool_call[\"args\"][\"answer\"]\n",
        "                    break\n",
        "            else:\n",
        "                # no tool call, we have a final answer\n",
        "                final_answer = tool_call.content\n",
        "                break\n",
        "\n",
        "        # Add to conversation history ONLY the human input and final AI response\n",
        "        # This preserves memory without corrupting it with tool calls\n",
        "        self.chat_history.extend([\n",
        "            HumanMessage(content=input),\n",
        "            AIMessage(content=final_answer)\n",
        "        ])\n",
        "\n",
        "        # return the final answer in dict form\n",
        "        return {\"output\": final_answer}\n",
        "\n",
        "# Initialize the custom agent executor\n",
        "conversational_agent = CustomAgentExecutor()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "cabbea50",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cabbea50",
        "outputId": "ce977a76-2250-417f-89e5-9cd39e8ecdcf"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0: calculator({'expression': '10000 * (1 + 0.08)**5'}) -> 14693.280768000006\n",
            "1: final_answer({'answer': 'The value of 10000 * (1 + 0.08)**5 is approximately 14693.28.', 'tools_used': ['functions.calculator']}) -> {'answer': 'The value of 10000 * (1 + 0.08)**5 is approximately 14693.28.', 'tools_used': ['functions.calculator']}\n",
            "Result: The value of 10000 * (1 + 0.08)**5 is approximately 14693.28.\n"
          ]
        }
      ],
      "source": [
        "# First question\n",
        "result = conversational_agent.invoke(\"What is 10000 * (1 + 0.08)**5?\")\n",
        "print(f\"Result: {result['output']}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bdcd9847",
      "metadata": {
        "id": "bdcd9847"
      },
      "source": [
        "As we can see below, the prompt is similar but it includes a great prelude of instructions that make it an effective assistant as well + a spot for including the chat history from the memory component:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "de413fd3",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "de413fd3",
        "outputId": "a16a8f2d-56ae-4bcf-c0bf-c70c188d2030"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Prompt Template Structure:\n",
            "==================================================\n",
            "System message: You're a helpful assistant. When answering a user's question you should first use one of the tools provided. After using a tool the tool output will be provided in the 'scratchpad' below. If you have an answer in the scratchpad you should not use any more tools and instead answer directly to the user. IMPORTANT: Use the analysis of previous calculations to avoid recalculating.\n",
            "History analysis template: Previous calculations analysis: {history_analysis}\n",
            "Human message template: {input}\n",
            "Variables: ['agent_scratchpad', 'history_analysis', 'input']\n",
            "==================================================\n"
          ]
        }
      ],
      "source": [
        "print(\"Prompt Template Structure:\")\n",
        "print(\"=\" * 50)\n",
        "print(\"System message:\", agent_prompt.messages[0].prompt.template)\n",
        "print(\"History analysis template:\", agent_prompt.messages[1].prompt.template)  # Show the memory component\n",
        "print(\"Human message template:\", agent_prompt.messages[2].prompt.template)\n",
        "print(\"Variables:\", agent_prompt.input_variables)\n",
        "print(\"=\" * 50)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "32ee8041",
      "metadata": {
        "id": "32ee8041"
      },
      "source": [
        "Let's see what happens if we try to answer the question that is related to the previous one:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "5e109878",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5e109878",
        "outputId": "4ba72c33-3fb6-42c1-e1d5-74000e87cba8"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0: calculator({'expression': '15000 * (1 + 0.08)**5'}) -> 22039.92115200001\n",
            "0: calculator({'expression': '15000 * (1 + 0.08)**5 - 14693.28'}) -> 7346.641152000009\n",
            "1: final_answer({'answer': 'If we start with $15,000 and follow the same 8% annual growth for 5 years with compound interest, we would have approximately $22,039.92. Compared to the previous scenario where we started with $10,000 and ended with approximately $14,693.28, we would have $7,346.64 more.', 'tools_used': ['multi_tool_use.parallel', 'functions.calculator', 'functions.calculator']}) -> {'answer': 'If we start with $15,000 and follow the same 8% annual growth for 5 years with compound interest, we would have approximately $22,039.92. Compared to the previous scenario where we started with $10,000 and ended with approximately $14,693.28, we would have $7,346.64 more.', 'tools_used': ['multi_tool_use.parallel', 'functions.calculator', 'functions.calculator']}\n",
            "Result: If we start with $15,000 and follow the same 8% annual growth for 5 years with compound interest, we would have approximately $22,039.92. Compared to the previous scenario where we started with $10,000 and ended with approximately $14,693.28, we would have $7,346.64 more.\n"
          ]
        }
      ],
      "source": [
        "result = conversational_agent.invoke(\n",
        "    \"If we start with $15,000 instead and follow the same 8% annual growth for 5 years with compound interest, how much more would we have compared to the previous scenario?\"\n",
        ")\n",
        "print(f\"Result: {result['output']}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "44135b8d",
      "metadata": {
        "id": "44135b8d"
      },
      "source": [
        "### Agent type #3: React Docstore"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1e6d0d13",
      "metadata": {
        "id": "1e6d0d13"
      },
      "source": [
        "This type of agent is similar to the ones we have seen so far but it includes the interaction with a docstore. It will have two and only two tools at its disposal: 'Search' and 'Lookup'."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d569fd41",
      "metadata": {
        "id": "d569fd41"
      },
      "source": [
        "With 'Search' it will bring up a relevant article and with 'Lookup' the agent will find the right piece of information in the article. This is probably easiest to see in an example:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "ecc452af",
      "metadata": {
        "id": "ecc452af"
      },
      "outputs": [],
      "source": [
        "from langchain_community.utilities import WikipediaAPIWrapper\n",
        "from langchain_core.tools import tool\n",
        "\n",
        "@tool\n",
        "def Search(query: str) -> str:\n",
        "    \"\"\"Search Wikipedia for information about a topic.\"\"\"\n",
        "    try:\n",
        "        wiki = WikipediaAPIWrapper()\n",
        "        return wiki.run(query)\n",
        "    except Exception as e:\n",
        "        return f\"Error searching Wikipedia: {e}\"\n",
        "\n",
        "@tool\n",
        "def Lookup(term: str) -> str:\n",
        "    \"\"\"Look up a specific term or phrase in Wikipedia.\"\"\"\n",
        "    try:\n",
        "        wiki = WikipediaAPIWrapper()\n",
        "        return wiki.run(term)\n",
        "    except Exception as e:\n",
        "        return f\"Error looking up term: {e}\"\n",
        "\n",
        "tools = [Search, Lookup]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "595938a1",
      "metadata": {
        "id": "595938a1"
      },
      "outputs": [],
      "source": [
        "# Create a custom agent executor for docstore tools\n",
        "docstore_prompt = ChatPromptTemplate.from_messages([\n",
        "    (\"system\", \"You're a helpful assistant that can search and lookup information.\"),\n",
        "    (\"human\", \"{input}\"),\n",
        "    MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
        "])\n",
        "\n",
        "docstore_agent_runnable = (\n",
        "    {\n",
        "        \"input\": lambda x: x[\"input\"],\n",
        "        \"agent_scratchpad\": lambda x: x.get(\"agent_scratchpad\", [])\n",
        "    }\n",
        "    | docstore_prompt\n",
        "    | llm.bind_tools(tools, tool_choice=\"auto\")\n",
        ")\n",
        "\n",
        "docstore_name2tool = {tool.name: tool.func for tool in tools}\n",
        "\n",
        "class DocstoreAgentExecutor:\n",
        "    def __init__(self, max_iterations: int = 3):\n",
        "        self.max_iterations = max_iterations\n",
        "        self.agent = docstore_agent_runnable\n",
        "\n",
        "    def invoke(self, input: str) -> dict:\n",
        "        count = 0\n",
        "        agent_scratchpad = []\n",
        "\n",
        "        while count < self.max_iterations:\n",
        "            tool_call = self.agent.invoke({\n",
        "                \"input\": input,\n",
        "                \"agent_scratchpad\": agent_scratchpad\n",
        "            })\n",
        "\n",
        "            agent_scratchpad.append(tool_call)\n",
        "\n",
        "            if not tool_call.tool_calls:\n",
        "                final_answer = tool_call.content\n",
        "                break\n",
        "\n",
        "            tool_name = tool_call.tool_calls[0][\"name\"]\n",
        "            tool_args = tool_call.tool_calls[0][\"args\"]\n",
        "            tool_call_id = tool_call.tool_calls[0][\"id\"]\n",
        "            tool_out = docstore_name2tool[tool_name](**tool_args)\n",
        "\n",
        "            tool_exec = ToolMessage(\n",
        "                content=f\"{tool_out}\",\n",
        "                tool_call_id=tool_call_id\n",
        "            )\n",
        "            agent_scratchpad.append(tool_exec)\n",
        "\n",
        "            print(f\"{count}: {tool_name}({tool_args}) = {tool_out[:100]}...\")\n",
        "            count += 1\n",
        "\n",
        "        return {\"input\": input, \"output\": final_answer}\n",
        "\n",
        "docstore_agent = DocstoreAgentExecutor()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "bba6b065",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bba6b065",
        "outputId": "cd8d50bd-a721-4a58-979f-9b49c9bd5cd1"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0: Lookup({'term': \"Archimedes' last words\"}) = Page: Archimedes\n",
            "Summary: Archimedes of Syracuse ( AR-kih-MEE-deez; c. 287 – c. 212 BC) was an Ancie...\n",
            "Result: The search did not return specific information about Archimedes' last words. However, historically it is often reported that Archimedes' last words were \"Do not disturb my circles,\" said to a Roman soldier who was about to kill him during the siege of Syracuse. This phrase reflects his deep engagement with his mathematical work even at the moment of his death.\n"
          ]
        }
      ],
      "source": [
        "result = docstore_agent.invoke(\"What were Archimedes' last words?\")\n",
        "print(f\"Result: {result['output']}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6a5f77ac",
      "metadata": {
        "id": "6a5f77ac"
      },
      "source": [
        "We will not print the prompt here because it is too large, but you can see it yourself if you want to (we know how to already).\n",
        "\n",
        "In short, it contains several examples of the `Question` > `Thought` > `Action` > `Observation` loop, that include the `Search` and `Lookup` tools.\n",
        "\n",
        "If you want to learn more about this approach [this](https://arxiv.org/pdf/2210.03629) is the paper for ReAct"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4cd80a08",
      "metadata": {
        "id": "4cd80a08"
      },
      "source": [
        "### Wrapping up"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "97d108e2",
      "metadata": {
        "id": "97d108e2"
      },
      "source": [
        "And that all for agents! There's many other things you can do with agents, just to name a few:\n",
        "* Create your own custom agent\n",
        "* Use them with many other tools (even custom ones)\n",
        "* Combine them with RAG tools for more dynamic search"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "pinecone1",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.4"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}